/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.impl.xs.traversers;

import java.util.ArrayList;
import java.util.Vector;

import com.sun.org.apache.xerces.internal.impl.dv.InvalidDatatypeFacetException;
import com.sun.org.apache.xerces.internal.impl.dv.SchemaDVFactory;
import com.sun.org.apache.xerces.internal.impl.dv.XSSimpleType;
import com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.xs.XSSimpleTypeDecl;
import com.sun.org.apache.xerces.internal.impl.xs.SchemaGrammar;
import com.sun.org.apache.xerces.internal.impl.xs.SchemaSymbols;
import com.sun.org.apache.xerces.internal.impl.xs.XSAnnotationImpl;
import com.sun.org.apache.xerces.internal.impl.xs.util.XInt;
import com.sun.org.apache.xerces.internal.impl.xs.util.XSObjectListImpl;
import com.sun.org.apache.xerces.internal.util.DOMUtil;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xs.XSConstants;
import com.sun.org.apache.xerces.internal.xs.XSObjectList;
import com.sun.org.apache.xerces.internal.xs.XSTypeDefinition;
import org.w3c.dom.Element;

/**
 * The simple type definition schema component traverser.
 *
 * <simpleType final = (#all | (list | union | restriction)) id = ID name = NCName {any attributes
 * with non-schema namespace . . .}> Content: (annotation?, (restriction | list | union))
 * </simpleType>
 *
 * <restriction base = QName id = ID {any attributes with non-schema namespace . . .}> Content:
 * (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive |
 * totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace |
 * pattern)*)) </restriction>
 *
 * <list id = ID itemType = QName {any attributes with non-schema namespace . . .}> Content:
 * (annotation?, (simpleType?)) </list>
 *
 * <union id = ID memberTypes = List of QName {any attributes with non-schema namespace . . .}>
 * Content: (annotation?, (simpleType*)) </union>
 *
 * @author Elena Litani, IBM
 * @author Neeraj Bajaj, Sun Microsystems, Inc.
 * @author Sandy Gao, IBM
 * @version $Id: XSDSimpleTypeTraverser.java,v 1.7 2010-11-01 04:40:02 joehw Exp $
 * @xerces.internal
 */
class XSDSimpleTypeTraverser extends XSDAbstractTraverser {

  // whether the type being parsed is a S4S built-in type.
  private boolean fIsBuiltIn = false;

  XSDSimpleTypeTraverser(XSDHandler handler,
      XSAttributeChecker gAttrCheck) {
    super(handler, gAttrCheck);
  }

  //return qualified name of simpleType or empty string if error occured
  XSSimpleType traverseGlobal(Element elmNode,
      XSDocumentInfo schemaDoc,
      SchemaGrammar grammar) {

    // General Attribute Checking
    Object[] attrValues = fAttrChecker.checkAttributes(elmNode, true, schemaDoc);
    String nameAtt = (String) attrValues[XSAttributeChecker.ATTIDX_NAME];
    if (nameAtt == null) {
      attrValues[XSAttributeChecker.ATTIDX_NAME] = NO_NAME;
    }
    XSSimpleType type = traverseSimpleTypeDecl(elmNode, attrValues, schemaDoc, grammar);
    fAttrChecker.returnAttrArray(attrValues, schemaDoc);

    // if it's a global type without a name, return null
    if (nameAtt == null) {
      reportSchemaError("s4s-att-must-appear",
          new Object[]{SchemaSymbols.ELT_SIMPLETYPE, SchemaSymbols.ATT_NAME}, elmNode);
      type = null;
    }

    // don't add global components without name to the grammar
    if (type != null) {
      if (grammar.getGlobalTypeDecl(type.getName()) == null) {
        grammar.addGlobalSimpleTypeDecl(type);
      }

      // also add it to extended map
      final String loc = fSchemaHandler.schemaDocument2SystemId(schemaDoc);
      final XSTypeDefinition type2 = grammar.getGlobalTypeDecl(type.getName(), loc);
      if (type2 == null) {
        grammar.addGlobalSimpleTypeDecl(type, loc);
      }

      // handle duplicates
      if (fSchemaHandler.fTolerateDuplicates) {
        if (type2 != null) {
          if (type2 instanceof XSSimpleType) {
            type = (XSSimpleType) type2;
          }
        }
        fSchemaHandler.addGlobalTypeDecl(type);
      }
    }

    return type;
  }

  XSSimpleType traverseLocal(Element elmNode,
      XSDocumentInfo schemaDoc,
      SchemaGrammar grammar) {

    // General Attribute Checking
    Object[] attrValues = fAttrChecker.checkAttributes(elmNode, false, schemaDoc);
    String name = genAnonTypeName(elmNode);
    XSSimpleType type = getSimpleType(name, elmNode, attrValues, schemaDoc, grammar);
    if (type instanceof XSSimpleTypeDecl) {
      ((XSSimpleTypeDecl) type).setAnonymous(true);
    }
    fAttrChecker.returnAttrArray(attrValues, schemaDoc);

    return type;
  }

  private XSSimpleType traverseSimpleTypeDecl(Element simpleTypeDecl,
      Object[] attrValues,
      XSDocumentInfo schemaDoc,
      SchemaGrammar grammar) {

    // get name and final values
    String name = (String) attrValues[XSAttributeChecker.ATTIDX_NAME];
    return getSimpleType(name, simpleTypeDecl, attrValues, schemaDoc, grammar);
  }

  /*
   * Generate a name for an anonymous type
   */
  private String genAnonTypeName(Element simpleTypeDecl) {

    // Generate a unique name for the anonymous type by concatenating together the
    // names of parent nodes
    // The name is quite good for debugging/error purposes, but we may want to
    // revisit how this is done for performance reasons (LM).
    StringBuffer typeName = new StringBuffer("#AnonType_");
    Element node = DOMUtil.getParent(simpleTypeDecl);
    while (node != null && (node != DOMUtil.getRoot(DOMUtil.getDocument(node)))) {
      typeName.append(node.getAttribute(SchemaSymbols.ATT_NAME));
      node = DOMUtil.getParent(node);
    }
    return typeName.toString();
  }

  /**
   * @param name
   * @param simpleTypeDecl
   * @param attrValues
   * @param schemaDoc
   * @param grammar
   * @return
   */
  private XSSimpleType getSimpleType(String name, Element simpleTypeDecl, Object[] attrValues,
      XSDocumentInfo schemaDoc, SchemaGrammar grammar) {
    XInt finalAttr = (XInt) attrValues[XSAttributeChecker.ATTIDX_FINAL];
    int finalProperty = finalAttr == null ? schemaDoc.fFinalDefault : finalAttr.intValue();
    // annotation?,(list|restriction|union)
    Element child = DOMUtil.getFirstChildElement(simpleTypeDecl);
    XSAnnotationImpl[] annotations = null;
    if (child != null && DOMUtil.getLocalName(child).equals(SchemaSymbols.ELT_ANNOTATION)) {
      XSAnnotationImpl annotation = traverseAnnotationDecl(child, attrValues, false, schemaDoc);
      if (annotation != null) {
        annotations = new XSAnnotationImpl[]{annotation};
      }
      child = DOMUtil.getNextSiblingElement(child);
    } else {
      String text = DOMUtil.getSyntheticAnnotation(simpleTypeDecl);
      if (text != null) {
        XSAnnotationImpl annotation = traverseSyntheticAnnotation(simpleTypeDecl, text, attrValues,
            false, schemaDoc);
        annotations = new XSAnnotationImpl[]{annotation};
      }
    }
    // (list|restriction|union)
    if (child == null) {
      reportSchemaError("s4s-elt-must-match.2",
          new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))"},
          simpleTypeDecl);
      return errorType(name, schemaDoc.fTargetNamespace, XSConstants.DERIVATION_RESTRICTION);
    }
    // derivation type: restriction/list/union
    String varietyProperty = DOMUtil.getLocalName(child);
    short refType = XSConstants.DERIVATION_RESTRICTION;
    boolean restriction = false, list = false, union = false;
    if (varietyProperty.equals(SchemaSymbols.ELT_RESTRICTION)) {
      refType = XSConstants.DERIVATION_RESTRICTION;
      restriction = true;
    } else if (varietyProperty.equals(SchemaSymbols.ELT_LIST)) {
      refType = XSConstants.DERIVATION_LIST;
      list = true;
    } else if (varietyProperty.equals(SchemaSymbols.ELT_UNION)) {
      refType = XSConstants.DERIVATION_UNION;
      union = true;
    } else {
      reportSchemaError("s4s-elt-must-match.1",
          new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))",
              varietyProperty}, simpleTypeDecl);
      return errorType(name, schemaDoc.fTargetNamespace, XSConstants.DERIVATION_RESTRICTION);
    }
    // nothing should follow this element
    Element nextChild = DOMUtil.getNextSiblingElement(child);
    if (nextChild != null) {
      reportSchemaError("s4s-elt-must-match.1",
          new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))",
              DOMUtil.getLocalName(nextChild)}, nextChild);
    }
    // General Attribute Checking: get base/item/member types
    Object[] contentAttrs = fAttrChecker.checkAttributes(child, false, schemaDoc);
    QName baseTypeName = (QName) contentAttrs[restriction ?
        XSAttributeChecker.ATTIDX_BASE :
        XSAttributeChecker.ATTIDX_ITEMTYPE];
    Vector memberTypes = (Vector) contentAttrs[XSAttributeChecker.ATTIDX_MEMBERTYPES];
    //content = {annotation?,simpleType?...}
    Element content = DOMUtil.getFirstChildElement(child);
    //check content (annotation?, ...)
    if (content != null && DOMUtil.getLocalName(content).equals(SchemaSymbols.ELT_ANNOTATION)) {
      XSAnnotationImpl annotation = traverseAnnotationDecl(content, contentAttrs, false, schemaDoc);
      if (annotation != null) {
        if (annotations == null) {
          annotations = new XSAnnotationImpl[]{annotation};
        } else {
          XSAnnotationImpl[] tempArray = new XSAnnotationImpl[2];
          tempArray[0] = annotations[0];
          annotations = tempArray;
          annotations[1] = annotation;
        }
      }
      content = DOMUtil.getNextSiblingElement(content);
    } else {
      String text = DOMUtil.getSyntheticAnnotation(child);
      if (text != null) {
        XSAnnotationImpl annotation = traverseSyntheticAnnotation(child, text, contentAttrs, false,
            schemaDoc);
        if (annotations == null) {
          annotations = new XSAnnotationImpl[]{annotation};
        } else {
          XSAnnotationImpl[] tempArray = new XSAnnotationImpl[2];
          tempArray[0] = annotations[0];
          annotations = tempArray;
          annotations[1] = annotation;
        }
      }
    }
    // get base type from "base" attribute
    XSSimpleType baseValidator = null;
    if ((restriction || list) && baseTypeName != null) {
      baseValidator = findDTValidator(child, name, baseTypeName, refType, schemaDoc);
      // if its the built-in type, return null from here
      if (baseValidator == null && fIsBuiltIn) {
        fIsBuiltIn = false;
        return null;
      }
    }
    // get types from "memberTypes" attribute
    ArrayList dTValidators = null;
    XSSimpleType dv = null;
    XSObjectList dvs;
    if (union && memberTypes != null && memberTypes.size() > 0) {
      int size = memberTypes.size();
      dTValidators = new ArrayList(size);
      // for each qname in the list
      for (int i = 0; i < size; i++) {
        // get the type decl
        dv = findDTValidator(child, name, (QName) memberTypes.elementAt(i),
            XSConstants.DERIVATION_UNION, schemaDoc);
        if (dv != null) {
          // if it's a union, expand it
          if (dv.getVariety() == XSSimpleType.VARIETY_UNION) {
            dvs = dv.getMemberTypes();
            for (int j = 0; j < dvs.getLength(); j++) {
              dTValidators.add(dvs.item(j));
            }
          } else {
            dTValidators.add(dv);
          }
        }
      }
    }

    // check if there is a child "simpleType"
    if (content != null && DOMUtil.getLocalName(content).equals(SchemaSymbols.ELT_SIMPLETYPE)) {
      if (restriction || list) {
        // it's an error for both "base" and "simpleType" to appear
        if (baseTypeName != null) {
          reportSchemaError(list ? "src-simple-type.3.a" : "src-simple-type.2.a", null, content);
        }
        if (baseValidator == null) {
          // traverse this child to get the base type
          baseValidator = traverseLocal(content, schemaDoc, grammar);
        }
        // get the next element
        content = DOMUtil.getNextSiblingElement(content);
      } else if (union) {
        if (dTValidators == null) {
          dTValidators = new ArrayList(2);
        }
        do {
          // traverse this child to get the member type
          dv = traverseLocal(content, schemaDoc, grammar);
          if (dv != null) {
            // if it's a union, expand it
            if (dv.getVariety() == XSSimpleType.VARIETY_UNION) {
              dvs = dv.getMemberTypes();
              for (int j = 0; j < dvs.getLength(); j++) {
                dTValidators.add(dvs.item(j));
              }
            } else {
              dTValidators.add(dv);
            }
          }
          // get the next element
          content = DOMUtil.getNextSiblingElement(content);
        } while (content != null && DOMUtil.getLocalName(content)
            .equals(SchemaSymbols.ELT_SIMPLETYPE));
      }
    } else if ((restriction || list) && baseTypeName == null) {
      // it's an error if neither "base/itemType" nor "simpleType" appears
      reportSchemaError(list ? "src-simple-type.3.b" : "src-simple-type.2.b", null, child);
    } else if (union && (memberTypes == null || memberTypes.size() == 0)) {
      // it's an error if "memberTypes" is empty and no "simpleType" appears
      reportSchemaError("src-union-memberTypes-or-simpleTypes", null, child);
    }
    // error finding "base" or error traversing "simpleType".
    // don't need to report an error, since some error has been reported.
    if ((restriction || list) && baseValidator == null) {
      fAttrChecker.returnAttrArray(contentAttrs, schemaDoc);
      return errorType(name, schemaDoc.fTargetNamespace,
          restriction ? XSConstants.DERIVATION_RESTRICTION : XSConstants.DERIVATION_LIST);
    }
    // error finding "memberTypes" or error traversing "simpleType".
    // don't need to report an error, since some error has been reported.
    if (union && (dTValidators == null || dTValidators.size() == 0)) {
      fAttrChecker.returnAttrArray(contentAttrs, schemaDoc);
      return errorType(name, schemaDoc.fTargetNamespace,
          XSConstants.DERIVATION_UNION);
    }
    // item type of list types can't have list content
    if (list && isListDatatype(baseValidator)) {
      reportSchemaError("cos-st-restricts.2.1", new Object[]{name, baseValidator.getName()}, child);
      fAttrChecker.returnAttrArray(contentAttrs, schemaDoc);
      return errorType(name, schemaDoc.fTargetNamespace,
          XSConstants.DERIVATION_LIST);
    }
    // create the simple type based on the "base" type
    XSSimpleType newDecl = null;
    if (restriction) {
      newDecl = fSchemaHandler.fDVFactory
          .createTypeRestriction(name, schemaDoc.fTargetNamespace, (short) finalProperty,
              baseValidator,
              annotations == null ? null : new XSObjectListImpl(annotations, annotations.length));
    } else if (list) {
      newDecl = fSchemaHandler.fDVFactory
          .createTypeList(name, schemaDoc.fTargetNamespace, (short) finalProperty, baseValidator,
              annotations == null ? null : new XSObjectListImpl(annotations, annotations.length));
    } else if (union) {
      XSSimpleType[] memberDecls = (XSSimpleType[]) dTValidators
          .toArray(new XSSimpleType[dTValidators.size()]);
      newDecl = fSchemaHandler.fDVFactory
          .createTypeUnion(name, schemaDoc.fTargetNamespace, (short) finalProperty, memberDecls,
              annotations == null ? null : new XSObjectListImpl(annotations, annotations.length));
    }
    // now traverse facets, if it's derived by restriction
    if (restriction && content != null) {
      FacetInfo fi = traverseFacets(content, baseValidator, schemaDoc);
      content = fi.nodeAfterFacets;

      try {
        fValidationState.setNamespaceSupport(schemaDoc.fNamespaceSupport);
        newDecl.applyFacets(fi.facetdata, fi.fPresentFacets, fi.fFixedFacets, fValidationState);
      } catch (InvalidDatatypeFacetException ex) {
        reportSchemaError(ex.getKey(), ex.getArgs(), child);
        // Recreate the type, ignoring the facets
        newDecl = fSchemaHandler.fDVFactory
            .createTypeRestriction(name, schemaDoc.fTargetNamespace, (short) finalProperty,
                baseValidator,
                annotations == null ? null : new XSObjectListImpl(annotations, annotations.length));
      }
    }
    // no element should appear after this point
    if (content != null) {
      if (restriction) {
        reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_RESTRICTION,
            "(annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern)*))",
            DOMUtil.getLocalName(content)}, content);
      } else if (list) {
        reportSchemaError("s4s-elt-must-match.1",
            new Object[]{SchemaSymbols.ELT_LIST, "(annotation?, (simpleType?))",
                DOMUtil.getLocalName(content)}, content);
      } else if (union) {
        reportSchemaError("s4s-elt-must-match.1",
            new Object[]{SchemaSymbols.ELT_UNION, "(annotation?, (simpleType*))",
                DOMUtil.getLocalName(content)}, content);
      }
    }
    fAttrChecker.returnAttrArray(contentAttrs, schemaDoc);
    // return the new type
    return newDecl;
  }

  //@param: elm - top element
  //@param: baseTypeStr - type (base/itemType/memberTypes)
  //@param: baseRefContext:  whether the caller is using this type as a base for restriction, union or list
  //return XSSimpleType available for the baseTypeStr, null if not found or disallowed.
  // also throws an error if the base type won't allow itself to be used in this context.
  // REVISIT: can this code be re-used?
  private XSSimpleType findDTValidator(Element elm, String refName,
      QName baseTypeStr, short baseRefContext,
      XSDocumentInfo schemaDoc) {
    if (baseTypeStr == null) {
      return null;
    }

    XSTypeDefinition baseType = (XSTypeDefinition) fSchemaHandler
        .getGlobalDecl(schemaDoc, XSDHandler.TYPEDECL_TYPE, baseTypeStr, elm);
    if (baseType == null) {
      return null;
    }
    if (baseType.getTypeCategory() != XSTypeDefinition.SIMPLE_TYPE) {
      reportSchemaError("cos-st-restricts.1.1", new Object[]{baseTypeStr.rawname, refName}, elm);
      return null;
    }

    // if it's a complex type, or if its restriction of anySimpleType
    if (baseType == SchemaGrammar.fAnySimpleType &&
        baseRefContext == XSConstants.DERIVATION_RESTRICTION) {
      // if the base type is anySimpleType and the current type is
      // a S4S built-in type, return null. (not an error).
      if (checkBuiltIn(refName, schemaDoc.fTargetNamespace)) {
        return null;
      }
      reportSchemaError("cos-st-restricts.1.1", new Object[]{baseTypeStr.rawname, refName}, elm);
      return null;
    }

    if ((baseType.getFinal() & baseRefContext) != 0) {
      if (baseRefContext == XSConstants.DERIVATION_RESTRICTION) {
        reportSchemaError("st-props-correct.3", new Object[]{refName, baseTypeStr.rawname}, elm);
      } else if (baseRefContext == XSConstants.DERIVATION_LIST) {
        reportSchemaError("cos-st-restricts.2.3.1.1", new Object[]{baseTypeStr.rawname, refName},
            elm);
      } else if (baseRefContext == XSConstants.DERIVATION_UNION) {
        reportSchemaError("cos-st-restricts.3.3.1.1", new Object[]{baseTypeStr.rawname, refName},
            elm);
      }
      return null;
    }

    return (XSSimpleType) baseType;
  }

  // check whethe the type denoted by the name and namespace is a S4S
  // built-in type. update fIsBuiltIn at the same time.
  private final boolean checkBuiltIn(String name, String namespace) {
    if (namespace != SchemaSymbols.URI_SCHEMAFORSCHEMA) {
      return false;
    }
    if (SchemaGrammar.SG_SchemaNS.getGlobalTypeDecl(name) != null) {
      fIsBuiltIn = true;
    }
    return fIsBuiltIn;
  }

  // find if a datatype validator is a list or has list datatype member.
  private boolean isListDatatype(XSSimpleType validator) {
    if (validator.getVariety() == XSSimpleType.VARIETY_LIST) {
      return true;
    }

    if (validator.getVariety() == XSSimpleType.VARIETY_UNION) {
      XSObjectList temp = validator.getMemberTypes();
      for (int i = 0; i < temp.getLength(); i++) {
        if (((XSSimpleType) temp.item(i)).getVariety() == XSSimpleType.VARIETY_LIST) {
          return true;
        }
      }
    }

    return false;
  }//isListDatatype(XSSimpleTypeDecl):boolean

  private XSSimpleType errorType(String name, String namespace, short refType) {
    XSSimpleType stringType = (XSSimpleType) SchemaGrammar.SG_SchemaNS.getTypeDefinition("string");
    switch (refType) {
      case XSConstants.DERIVATION_RESTRICTION:
        return fSchemaHandler.fDVFactory.createTypeRestriction(name, namespace, (short) 0,
            stringType, null);
      case XSConstants.DERIVATION_LIST:
        return fSchemaHandler.fDVFactory.createTypeList(name, namespace, (short) 0,
            stringType, null);
      case XSConstants.DERIVATION_UNION:
        return fSchemaHandler.fDVFactory.createTypeUnion(name, namespace, (short) 0,
            new XSSimpleType[]{stringType}, null);
    }

    return null;
  }

}//class XSDSimpleTypeTraverser
